package TEC;

import static TEC.Tec.codeTable;
import static TEC.Tec.drawLine;

public class SubRoutine implements Runnable {
    Thread thread;
    private Tec tec;

    SubRoutine(Tec tec) {
        thread = new Thread(this);
        this.tec = tec;
        System.out.println("subroutine on!");
        thread.setDaemon(true);
        thread.start();
    }

    public void run() {
        int offset, adr;
        boolean jr = false;
        while (tec.cursor > 0 && !thread.isInterrupted()) {
            if (tec.memory.get(tec.cursor) == null) {
                drawLine.newLine(tec.textArea1, "UnrecognizedCode!");
                break;
            } else {
                String code = tec.memory.get(tec.cursor);
                String[] order = codeTable.getOrderAndParameterByCode(code);
                if (order[0].equals("UnrecognizedCode!")) {
                    drawLine.newLine(tec.textArea1, "UnrecognizedCode!");
                    break;
                } else {
                    switch (order[0]) {
                        case "ADD":
                            ADD(order);
                            break;
                        case "SUB":
                            SUB(order);
                            break;
                        case "AND":
                            AND(order);
                            break;
                        case "CMP":
                            CMP(order);
                            break;
                        case "XOR":
                            XOR(order);
                            break;
                        case "TEST":
                            TEST(order);
                            break;
                        case "OR":
                            OR(order);
                            break;
                        case "MVRR":
                            MVRR(order);
                            break;
                        case "DEC":
                            DEC(order);
                            break;
                        case "INC":
                            INC(order);
                            break;
                        case "SHL":
                            SHL(order);
                            break;
                        case "SHR":
                            SHR(order);
                            break;
                        case "MVRD":
                            tec.cursor++;
                            order[2] = tec.memory.get(tec.cursor);
                            MVRD(order);
                            break;
                        case "CALA":
                            tec.sp.push(++tec.cursor);
                            adr = Integer.parseInt(tec.memory.get(tec.cursor), 16);
                            tec.cursor = adr;
                            jr = true;
                            break;
                            //todo CALA
                        case "OUT":
                            OUT(order);
                            break;
                        case "RET":
                            RET();
                            break;
                        case "IN":
                            IN(order);
                            break;
                        case "JR":
                            offset = Integer.parseInt(order[1], 16);
                            adr = ((tec.cursor & 0xff) + (offset & 0xff) + 1) & 0xff;
                            adr += (tec.cursor & 0xff00);
                            tec.cursor = adr;
                            jr = true;
                            break;
                        case "JRC":
                            if (tec.C.equals("1")) {
                                offset = Integer.parseInt(order[1], 16);
                                adr = ((tec.cursor & 0xff) + (offset & 0xff) + 1) & 0xff;
                                adr += (tec.cursor & 0xff00);
                                tec.cursor = adr;
                                jr = true;
                            }
                            break;
                        case "JRNC":
                            if (tec.C.equals("0")) {
                                offset = Integer.parseInt(order[1], 16);
                                adr = ((tec.cursor & 0xff) + (offset & 0xff) + 1) & 0xff;
                                adr += (tec.cursor & 0xff00);
                                tec.cursor = adr;
                                jr = true;
                            }
                            break;
                        case "JRZ":
                            if (tec.Z.equals("1")) {
                                offset = Integer.parseInt(order[1], 16);
                                adr = ((tec.cursor & 0xff) + (offset & 0xff) + 1) & 0xff;
                                adr += (tec.cursor & 0xff00);
                                tec.cursor = adr;
                                jr = true;
                            }
                            break;
                        case "JRNZ":
                            if (tec.Z.equals("0")) {
                                offset = Integer.parseInt(order[1], 16);
                                adr = ((tec.cursor & 0xff) + (offset & 0xff) + 1) & 0xff;
                                adr += (tec.cursor & 0xff00);
                                tec.cursor = adr;
                                jr = true;
                            }
                            break;
                        case "JMPA":
                            tec.cursor++;
                            adr = Integer.parseInt(tec.memory.get(tec.cursor), 16);
                            tec.cursor = adr;
                            jr = true;
                            break;


                    }
                }
            }

            if (!jr) {
                tec.cursor++;
            }
            jr = false;
            try {
                Thread.sleep(6);
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
                break;
            }
        }
        System.out.println("subroutine down!");
        drawLine.newLine(tec.textArea1, ">");
    }

    private void SetR(int num, String r) {
        tec.R[num] = r;
    }

    private void SetC(String c) {
        tec.C = c;
    }

    private void SetZ(String z) {
        tec.Z = z;
    }

    private void CheckZ(String ALU) {
        if (Integer.parseInt(ALU) == 0) {
            SetZ("1");
        } else {
            SetZ("0");
        }
    }

    private void CheckZ(int ALU) {
        if (ALU == 0) {
            SetZ("1");
        } else {
            SetZ("0");
        }
    }

    private String GetR(int num) {
        return tec.R[num];
    }

    private String GetPort(int portNum) {
        return tec.port.get(portNum);
    }

    private synchronized void ADD(String[] order) {
        int r1 = Integer.parseInt(GetR(Integer.parseInt(order[1], 16)), 16);
        int r2 = Integer.parseInt(GetR(Integer.parseInt(order[2], 16)), 16);
        int ALU = r1 + r2;
        if (ALU > 65535) {
            SetC("1");
        } else {
            SetC("0");
        }
        ALU = ALU & 0xffff;
        CheckZ(ALU);
        SetR(Integer.parseInt(order[1], 16), Integer.toHexString(ALU));
    }

    private synchronized void SUB(String[] order) {
        int r1 = Integer.parseInt(GetR(Integer.parseInt(order[1], 16)), 16);
        int r2 = Integer.parseInt(GetR(Integer.parseInt(order[2], 16)), 16);
        r2 = (~r2) + 1;
        int ALU = r1 + r2;
        ALU = ALU & 0xffff;
        CheckZ(ALU);
        SetR(Integer.parseInt(order[1], 16), Integer.toHexString(ALU));
    }

    private synchronized void AND(String[] order) {
        int r1 = Integer.parseInt(GetR(Integer.parseInt(order[1], 16)), 16);
        int r2 = Integer.parseInt(GetR(Integer.parseInt(order[2], 16)), 16);
        int ALU = r1 & r2;
        ALU = ALU & 0xffff;
        CheckZ(ALU);
        SetR(Integer.parseInt(order[1], 16), Integer.toHexString(ALU));
    }

    private synchronized void XOR(String[] order) {
        int r1 = Integer.parseInt(GetR(Integer.parseInt(order[1], 16)), 16);
        int r2 = Integer.parseInt(GetR(Integer.parseInt(order[2], 16)), 16);
        int ALU = r1 ^ r2;
        ALU = ALU & 0xffff;
        CheckZ(ALU);
        SetR(Integer.parseInt(order[1], 16), Integer.toHexString(ALU));
    }

    private synchronized void TEST(String[] order) {
        int r1 = Integer.parseInt(GetR(Integer.parseInt(order[1], 16)), 16);
        int r2 = Integer.parseInt(GetR(Integer.parseInt(order[2], 16)), 16);
        int ALU = r1 & r2;
        ALU = ALU & 0xffff;
        CheckZ(ALU);
    }

    private synchronized void OR(String[] order) {
        int r1 = Integer.parseInt(GetR(Integer.parseInt(order[1], 16)), 16);
        int r2 = Integer.parseInt(GetR(Integer.parseInt(order[2], 16)), 16);
        int ALU = r1 | r2;
        ALU = ALU & 0xffff;
        CheckZ(ALU);
        SetR(Integer.parseInt(order[1], 16), Integer.toHexString(ALU));
    }

    private synchronized void MVRR(String[] order) {
        SetR(Integer.parseInt(order[1], 16), GetR(Integer.parseInt(order[2], 16)));
    }

    private synchronized void DEC(String[] order) {
        int r1 = Integer.parseInt(GetR(Integer.parseInt(order[1], 16)), 16);
        int ALU = r1 + 0xffff;
        if (ALU > 0xffff) {
            SetC("1");
        } else {
            SetC("0");
        }
        ALU = ALU & 0xffff;
        CheckZ(ALU);
        SetR(Integer.parseInt(order[1], 16), Integer.toHexString(ALU));
    }

    private synchronized void INC(String[] order) {
        int r1 = Integer.parseInt(GetR(Integer.parseInt(order[1], 16)), 16);
        int ALU = r1 + 1;
        if (ALU > 0xffff) {
            SetC("1");
        } else {
            SetC("0");
        }
        ALU = ALU & 0xffff;
        CheckZ(ALU);
        SetR(Integer.parseInt(order[1], 16), Integer.toHexString(ALU));
    }

    private synchronized void SHL(String[] Order) {
        String string = "";
        String R = GetR(Integer.parseInt(Order[1]));
        string = tec.CompleteBinary(Integer.toString(Integer.parseInt(R, 16), 2));
        string = "0" + string.substring(0, string.length() - 1);
        string = Integer.toHexString(Integer.parseInt(string, 2)).toUpperCase();
        CheckZ(string);
        SetR(Integer.parseInt(Order[1]), string);
    }

    private synchronized void SHR(String[] Order) {
        String string = "";
        String R = GetR(Integer.parseInt(Order[1]));
        string = tec.CompleteBinary(Integer.toString(Integer.parseInt(R, 16), 2));
        SetC(string.substring(0, 1));
        string = string.substring(1) + "0";
        string = Integer.toHexString(Integer.parseInt(string, 2)).toUpperCase();
        CheckZ(string);
        SetR(Integer.parseInt(Order[1]), string);
    }

    private synchronized void MVRD(String[] order) {
        SetR(Integer.parseInt(order[1], 16), order[2]);
    }

    private synchronized void OUT(String[] order) {
        StringBuilder unicode = new StringBuilder();
        unicode.append((char) Integer.parseInt(tec.R[0], 16));
        if (order[1].equals("80")) {
            drawLine.AddCharToLastLine(tec.textArea1, unicode.toString());
        } else if (order[1].equals("90")) {
            drawLine.AddCharToLastLine(tec.textArea2, unicode.toString());
        }
    }

    private synchronized void RET() {
        if (tec.sp.empty()) {
            tec.cursor = -1;
        } else {
            tec.cursor = (int) tec.sp.pop();
        }

    }

    private synchronized void Jr(String[] order) {
        tec.cursor = Integer.parseInt(order[1], 16) - 1;
    }

    private synchronized void IN(String[] Order) {
        SetR(0, GetPort(Integer.parseInt(Order[1])));
        tec.port.put(Integer.parseInt(Order[1]), "0000");
    }



    private synchronized void CMP(String[] Order) {
        int res = Integer.parseInt(GetR(Integer.parseInt(Order[1], 16)), 16) - Integer.parseInt(GetR(Integer.parseInt(Order[2], 16)), 16);
        if (res >= 0) {
            SetC("1");
        } else {
            SetC("0");
        }
        CheckZ(res);
    }
}
